<?php
/**
 * Copyright (c) 2010, Slawomir Mazur <mazursllawek@gmail.com>
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions 
 * are met:
 * 
 * o Redistributions of source code must retain the above copyright notice, 
 *   this list of conditions and the following disclaimer.
 * o Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * o Neither the name of the Softisland nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * Enter description here...
 *
 * @package     System
 * @copyright   Slawomir Mazur (c) 2010 (http://www.zend.com/zce.php?c=ZEND011643)
 * @license     http://www.opensource.org/licenses/bsd-license.php
 * @author      Slawomir Mazur <mazursllawek@gmail.com>
 */

class System_Model_Network extends Zend_Db_Table_Abstract
{
	protected $_name = 'network';
	
	protected $_primary = 'network_id';
	
	protected $_collection = null;
	
	/**
	 * Get networks collection
	 * 
	 * @return Zend_Db_Table_Rowset_Abstract
	 */
	public function getCollection()
	{
		$select = $this->select()
				->setIntegrityCheck(false)
				->from(array('n' => $this->_name), array('network_id'))
				->join(array('l' => 'library'), 'l.library_id = n.library_id', array('name'))
				->order(array('l.name'));
		
		return $this->fetchAll($select);
	}
	
	public function getNetworkById($id)
	{
		$id = (int) $id;
		$result = $this->find($id);
		
		if (!$result->count()) {
			throw new Exception('Network with the given id doesn\'t exists');
		}
		return $result->current();
	}
	
	public function getNetworksAsArray()
	{
		$collection = $this->getCollection();
		
		$data = array();
		if ($collection->count() > 0) {
			foreach ($collection as $row) {
				$data[$row->network_id] = $row->name;
			}
		}
		return $data;
	}
	
	public function getNetworkByLibraryId($id)
	{
		$id = (int) $id;
		
		$select = $this->select()
				->setIntegrityCheck(false)
				->from(array('n' => $this->_name))
				->join(array('l' => 'library'), 'c.library_id = l.library_id')
				->where('library_id = ?', $id)
				->limit(1);

		$network = $this->fetchRow($select);
		
		if (empty($network)) {
			return '';
		}
		return $network;
	}
	
	/**
	 * Generates network file for a given library
	 * 
	 * @param int $libraryId
	 * @param string $gene gene symbol
	 * 
	 * @return string network file name
	 */
	public function generateNetworkForLibrary($libraryId, $gene)
	{
		$networkHelper = new Gp_View_Helper_Network();
		if (false !== ($file = $networkHelper->checkIfNetworkExists($libraryId, $gene))) {
			//return $file;
		}
		
		$select = $this->getAdapter()->select();		
		$select->from('history', 
					array('gene_id', 'gene_symbol', 'sirna_id', 'sequence', 'is_off_target', 
						'off_target_gene_ids', 'off_target_gene_names')
				)
				->where('library_id = ?', $libraryId, Zend_Db::INT_TYPE);
				
		$result = $this->getAdapter()->fetchAll($select);
		
		if (empty($result)) {
			throw new Exception('Wrong library!');
		}
		
		// init data switch default index on gene based
		$dataRows = array();
		foreach ($result as $row) {
			$geneSymbol = trim($row['gene_symbol']);
			if (isset($dataRows[$geneSymbol])) {
				$dataRows[$geneSymbol][] = $row;
			} else {
				$dataRows[$geneSymbol] = array($row);
			}
		}
		
		// initial state
		$printData = array();
		
		if (!isset($dataRows[$gene])) {
			throw new Exception('Specified gene doesn\'t existst in database!');
		}
		
		// retrive first level data
		$this->_getDataForGene($dataRows, $printData, $gene);
		
		$verticies = array();
    	$edges = array();
    	$parents = array();
    	// nodes without parents
    	$orphans = array();
		
    	//print_r($printData); exit;
    	
		foreach ($printData as $gene => $printRow) {
    		foreach ($printRow as $index => $dataRow) {
    			// off target gene
	    		if (isset($dataRow['off_target_names'])) {
		    		foreach ($dataRow['off_target_names'] as $offName => $offs) {
		    			// gene that are in data base
		    			if (is_array($offs)) {
		    				foreach ($offs as $offRow) {
		    					$edges[] = array(
		    						$dataRow['gene_symbol'] . '_' . $dataRow['sequence'] => $offRow['gene_symbol'] . '_' . $offRow['sequence']
		    					);
		    					$verticies[$dataRow['gene_symbol'] . '_' . $dataRow['sequence']] = array(
		    						'parent' => $dataRow['gene_symbol'],
		    						'id' => $dataRow['gene_id'],
		    						'sirnaid' => $dataRow['sirna_id']
		    					);
		    					$verticies[$offRow['gene_symbol'] . '_' . $offRow['sequence']] = array(
		    						'parent' => $offRow['gene_symbol'],
		    						'id' => $offRow['gene_id'],
		    						'sirnaid' => $offRow['sirna_id']
		    					);	    					
		    					$parents[$dataRow['gene_symbol']] = $dataRow['gene_symbol'];
		    					$parents[$offRow['gene_symbol']] = $offRow['gene_symbol'];	    					
		    				}
		    			
		    			// gene doesn't exists in data base 
		    			} else {
		    				$edges[] = array(
		    					$dataRow['gene_symbol'] . '_' . $dataRow['sequence'] => $offName . '_'
		    				);
		    				$verticies[$dataRow['gene_symbol'] . '_' . $dataRow['sequence']] = array(
		    					'parent' => $dataRow['gene_symbol'],
		    					'id' => $dataRow['gene_id'],
		    					'sirnaid' => $dataRow['sirna_id']
		    				);		    				
		    				$parents[$dataRow['gene_symbol']] = $dataRow['gene_symbol'];
		    				$orphans[$offName . '_'] = $offName;
		    			}
		    		}
		    	
		    	// on target or null gene ids
	    		} else {
	    			$edges[] = array($dataRow['gene_symbol'] . '_' . $dataRow['sequence'] => '');
	    			$verticies[$dataRow['gene_symbol'] . '_' . $dataRow['sequence']] = array(
	    				'parent' => $dataRow['gene_symbol'],
	    				'id' => $dataRow['gene_id'],
	    				'sirnaid' => $dataRow['sirna_id']
	    			);
	    			$parents[$dataRow['gene_symbol']] = $dataRow['gene_symbol'];
	    		}
	    	}
    	}
    	
    	// repair egdes because of repetitions
		for ($index = 0; $index < count($edges); $index++) {
			foreach ($edges[$index] as $from => $to) {
				$this->_getEdgeType($edges, $index, $from, $to);
			}
		}
		
		// pass data to helper and generate network file
		return $networkHelper->network($parents, $verticies, $orphans, $edges, $libraryId, $gene);
	}
	
	/**
	 * Get data for each off target gene id for given gene given
	 * by $geneId
	 * 
	 * @param array $fullData result of initial select
	 * @param array $printData data for network
	 * @param int $geneId
	 */
	protected function _getDataForGene($fullData, &$printData, $geneSymbol = null)
	{
    	// recurence detection
    	if (isset($printData[$geneSymbol])) {
    		return;
    	}
    	
    	if (empty($geneSymbol)) {
    		throw new Exception('Empty gene in generating data!');
    	}
    	
    	// setup for current gene id
    	$printData[$geneSymbol] = $fullData[$geneSymbol];
    	
    	// get off target gene ids and convert them to int's    	
    	foreach ($printData[$geneSymbol] as $index => $dataRow) {
			if ('no' == $dataRow['is_off_target']) {
				unset($printData[$geneSymbol][$index]['off_target_gene_names']);
				continue;
			}
			$printData[$geneSymbol][$index]['off_target_names'] = 
				$this->_getOffTargetGeneData($dataRow);
			unset($printData[$geneSymbol][$index]['off_target_gene_names']);
		}
		
		// based on previous parsed off gene ids retrive data
		// from original table and switch with proper off gene id
    	foreach ($printData[$geneSymbol] as $index => $dataRow) {
			if ('no' == $dataRow['is_off_target']) {
				continue;
			}
			
			if (!isset($dataRow['off_target_names'])) {
				throw new Exception('Empty off target names at wrong place!');
			}
			
			foreach ($dataRow['off_target_names'] as $offGene) {
				if (isset($fullData[$offGene])) {
					$printData[$geneSymbol][$index]['off_target_names'][$offGene] = $fullData[$offGene];
					
					// recurence for each off gene id
					$this->_getDataForGene($fullData, $printData, $offGene);
				}
			}
    	}
	}
	
	/**
	 * Get array containig gene name's based on string. Explode
	 * string and trim each element (gene symbol) to int for better
	 * performance
	 * 
	 * @param string $offTargetGenesString
	 * @return array
	 */
	protected function _getOffTargetGeneData($data) 
	{
		$offTargetGeneData = array();
		//$ids = explode(',', $data['off_target_gene_ids']);
		$symbols = explode(',', $data['off_target_gene_names']);
		
		//if (count($ids) != count($symbols)) {
			//throw new Exception('Wrong data number: names != ids');
		//}
		
		for ($i = 0; $i < count($symbols); $i++) {
			//$offGeneId = (int)($ids[$i]);
			$offGeneSymbol = trim($symbols[$i]);
			$offTargetGeneData[$offGeneSymbol] = $offGeneSymbol;
		}
		return $offTargetGeneData;
	}
	
	/**
	 * Validate edges types
	 * 
	 * @param array $edges edges
	 * @param int $index current edge index
	 * @param string $from source node
	 * @param string $to target node
	 */
	protected function _getEdgeType(&$edges, $index, $from, $to) {	
		for ($i = $index + 1; $i < count($edges); $i++) {		
			foreach ($edges[$i] as $f => $t) {			
				if (($f == $to) && ($t == $from)) {
					$edges[$i]['type'] = 'BOTH';
					$edges[$index]['type'] = 'SKIP';
				} else if ($from == $f) {
					if (!isset($edges[$i]['type']) ||
						(isset($edges[$i]['type']) && (false === strpos($edges[$i]['type'], 'BOTH')))) {
							
						$edges[$i]['type'] = 'SOURCE';
					}
				}
			}
		}
	}
}